package org.activequant.container.context;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.activequant.container.ITradeSystemContextThread;
import org.activequant.core.domainmodel.data.MarketDataEntity;
import org.activequant.data.retrieval.IMarketDataReplayService;
import org.apache.log4j.Logger;

/**
 * An ITradeSystemContextThread implementation. Holds the following associated variables:
 * <ul>
 * <li>replayService(IMarketDataReplayService)</li>
 * <li>delay(AtomicLong)</li>
 * <li>isRunning(AtomicBoolean)</li>
 * </ul>
 * <br>
 * <b>History:</b><br>
 *  - [17.01.2008] Created (Erik Nijkamp)<br>
 *
 *  @author Erik Nijkamp
 */
public class DelayedBacktestRunner implements ITradeSystemContextThread {
	
	private static final Logger log = Logger.getLogger(DelayedBacktestRunner.class);
	/**
	 * private IMarketDataReplayService replayService;
	 */
	private IMarketDataReplayService replayService;
	/**
	 * private AtomicLong delay = new AtomicLong(1000);
	 */
	private AtomicLong delay = new AtomicLong(1000);
	/**
	 * private AtomicBoolean isRunning = new AtomicBoolean(true);
	 */
	private AtomicBoolean isRunning = new AtomicBoolean(true); 
	/**
	 * an empty DelayedBacktestRunner(ITradeSystemContextThread implementation) constructor
	 */
	public DelayedBacktestRunner() { }
	/**
	 * constructs a DelayedBacktestRunner(ITradeSystemContextThread implementation) using the given replayService(IMarketDataReplayService) to set its associated replayService(IMarketDataReplayService)
	 * @param replayService
	 */
	public DelayedBacktestRunner(IMarketDataReplayService replayService) {
		this.replayService = replayService;
	}
	/**
	 * iterates through each entity(MarketDataEntity&lt;?&gt;) in the associated replayService(IMarketDataReplayService) invoking
	 * <code>replayService.fire(entity)</code> on each. It has the ability to pause/resume and delay between each loop.
	 */
	public void run() {
		log.info("running delayed backtester");
		int count = 0;
		try {
			for (MarketDataEntity<?> entity : replayService) {
				hang();
				++count;
				replayService.fire(entity);
				log.info("fired " + entity + " for instrument: " + entity.getInstrumentSpecification());
				delay();
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			log.error(ex);
		}
		log.info("backtester completed. fired " + count + " events");
	}
	/**
	 * causes the current thread to keep sleeping in intervals of 100ms at a time until the associated isRunning(AtomicBoolean) is false, or it gets interrupted
	 */
	private void hang() {
		while (!isRunning.get()) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				/* Ignored */
			}
		}
	}
	/**
	 * causes the current thread to sleep for the associated delay(AtomicLong) milliseconds (a 1000 by default) unless it gets interrupted
	 */
	private void delay() {
		try {
			Thread.sleep(delay.get());
		} catch(InterruptedException ex) {
			/* Ignored */
		}
	}
	/**
	 * returns the associated replayService(IMarketDataReplayService)
	 * @return
	 */
	public IMarketDataReplayService getReplayService() {
		return replayService;
	}
	/**
	 * sets the associated replayService(IMarketDataReplayService) with the given replayService(IMarketDataReplayService)
	 * @param replayService
	 */
	public void setReplayService(IMarketDataReplayService replayService) {
		this.replayService = replayService;
	}
	/**
	 * returns the value of the associated delay(AtomicLong)
	 * @return
	 */
	public long getDelay() {
		return delay.get();
	}
	/**
	 * sets the value of the associated delay(AtomicLong) with the given ms(long)
	 * @param ms
	 */
	public void setDelay(long ms) {
		this.delay.set(ms);
	}
	/**
	 * decreases the value of the associated delay(AtomicLong) for the given ms(long) (but the value can't go lower than 0)
	 * @param ms
	 */
	public void accelerate(long ms) {
		if(delay.get() - ms > 0) {
			delay.set(delay.get() - ms);
		} else {
			delay.set(0);
		}
	}
	/**
	 * increases the value of the associated delay(AtomicLong) for the given ms(long)
	 * @param ms
	 */
	public void decelerate(long ms) {
		delay.set(delay.get() + ms);	
	}
	/**
	 * sets the value of the associated isRunning(AtomicBoolean) to false
	 */
	public void pause() {
		isRunning.set(false);
	}
	/**
	 * sets the value of the associated isRunning(AtomicBoolean) to true
	 */
	public void resume() {
		isRunning.set(true);
	}
}
